package net.cyndeline.scalarlib.rldrawing

import scalax.collection.immutable.Graph
import net.cyndeline.scalarlib.rlgraph.cartogram.rectangular.segmentHeuristic.{SegmentHeuristic, MapArea}
import scalax.collection.GraphEdge.UnDiEdge
import net.cyndeline.scalarlib.rldrawing.rectangularFloorPlan.FloorPlan
import net.cyndeline.scalarlib.rlgraph.planarGraphDrawing.rectangular.{RAlgorithmSettings, RectangularDualAlgorithm}
import net.cyndeline.scalarlib.rldrawing.util.Point
import net.cyndeline.scalarlib.rldrawing.rectangularFloorPlan.help.{MinimumSizeIncrease, ChocoMinSize, RoomSizeIncreaseAlg}
import scala.reflect.runtime.universe.TypeTag
import scala.reflect.ClassTag

/**
 * This is the main factory object used to create floor plans. Only access the individual classes when sub-algorithms
 * needs to be substituted for others.
 *
 * The algorithm performs the following steps, given an input graph G:
 *
 *  1. The graph is converted to a rectangular dual
 *  1. The segments of the dual are moved in order to resize each rectangular area closer to its target size.
 *  1. The smallest increase needed to scale the optimized layout such that every room meets its target area is
 *  computed using a heuristic that prioritizes keeping the aspect ratio even.
 *  1. Room that exceed its target area are reduced in size from one side, such that adjacencies are kept intact.
 *
 * Options regarding the final drawing may be specified, but default settings can also be used.
 */
object FloorPlanArchitect {

  def buildDrawing[V <: MapArea : TypeTag : ClassTag, E[X] <: UnDiEdge[X] : ({type l[M[_]] = TypeTag[M[V]]})#l]
  (graph: Graph[V, E]): FloorPlan[V, E] = buildDrawingWithSettings(graph, new FloorPlanSettings[V, E]())

  def buildDrawingWithSettings[V <: MapArea : TypeTag : ClassTag, E[X] <: UnDiEdge[X] : ({type l[M[_]] = TypeTag[M[V]]})#l]
    (graph: Graph[V, E], settings: FloorPlanSettings[V, E]): FloorPlan[V, E] = {
    require(settings.minimumSideSize >= 3, "The minimum size of each room side must be 3 or more (2 walls and 1 floor tile).")
    require(settings.maxAspectRatio >= 1, "Aspect ratios must be 1 or higher.")
    val minSizeAddition: MinimumSizeIncrease = new ChocoMinSize()

    /* Step 1: Compute the dual. */
    val algorithmSettings = if (settings.outerFaceSelection.isDefined)
      RAlgorithmSettings[V, E]().withOuterFaceSelect(settings.outerFaceSelection.get)
    else
      RAlgorithmSettings[V, E]()

    val rAlgorithm = RectangularDualAlgorithm.regularAlgorithmWithSettings(algorithmSettings)
    val initialDual = rAlgorithm.computeLayout(graph)

    var i = 0
    var current = initialDual.mergeDummyRectangles // Get rid of as many dummy rectangles as possible, as they hinder segment movement
    while (i < 10) {

      /* Step 2: Optimize segments. */
      val optimizedDual = new SegmentHeuristic(settings.maxAspectRatio).applyToLayoutWithExceptions(current, settings.heuristicExceptions)

      /* Step 3: Increase every rectangle in size such that every room meets its target area.
       *
       * Note !!! This algorithm is responsible for ensuring that every adjacency constraint has 1 extra tile margin,
       * to prevent the player from moving diagonally between walls at an intersection.
       */
      current = minSizeAddition.increaseToMinimumWithExceptions(optimizedDual, settings.heuristicExceptions, settings.minimumSideSize)

      i += 1
    }

    FloorPlan(current)
  }
}
